# encoding: utf-8

import wsgiref.handlers
import os
import re
import cgi
import datetime
import sys

from google.appengine.api import users
from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.api import memcache


import models
import functions
import cache
#import options


class BaseRequestHandler(webapp.RequestHandler):
    """Supplies a common template generation function.
    
    When you call generate(), we augment the template variables supplied with
    the current user in the 'user' variable and the current webapp request
    in the 'request' variable.
    """
    
    def generate(self, template_name, template_values={}):
        options = functions.get_options()
        self.user = users.get_current_user()
        if not self.user:
            self.redirect(users.create_login_url(self.request.uri))
        values = {
            'request': self.request,
            'user': users.GetCurrentUser(),
            'login_url': users.CreateLoginURL(self.request.uri),
            'logout_url': users.CreateLogoutURL('http://' + self.request.host + '/'),
            'options': options
        }
        values.update(template_values)
        directory = os.path.dirname(__file__)
        path = os.path.join(directory, os.path.join('templates/admin', template_name))
        self.response.out.write(template.render(path, values, debug=options.DEBUG))
    
    def generate_js(self, html):
        self.response.headers["Content-Type"] = "text/html"
        self.response.out.write('<script language="JavaScript">'+ html + '</script>')
    
    def generate_js_error(self,title='Error',text=''):
        self.generate_js("Growl.Error({title:'%s', text: '%s'});" % (title,text))
    
    def generate_js_action(self,title='Action',text='',extra=''):
        self.generate_js("Growl.Action({title:'%s', text: '%s'});%s" % (title,text,extra))


class NotFound(BaseRequestHandler):
    """Returns a Not Found page"""
    def get(self,error):
        self.generate('admin.html', {'content': '<h2>Page not Found</h2>'})

       
class CategoriesPage(BaseRequestHandler):
    """/admin/categories - Admin page for categories."""
    def get(self):
        values = {
            'categories': models.Category.all()
        }
        self.generate('categories.html', values)


class NewCategory(CategoriesPage):
    """/admin/categories/new - Create a new category"""
    def post(self):
        category_name = unicode(self.request.get('category_name').strip(),'utf-8') #Avoid white space categories
        if not category_name:
            self.generate_js_error('No category name introduced')
        else:
            category_url = category_name.lower().replace(' ', '_')
            category_key = 'cat_' + category_url
            new_category = models.Category.get_by_key_name(category_key)
            if new_category:
                self.generate_js_error('Category exists')
            else:
                new_category=models.Category(key_name=category_key, 
                                             category_name=category_name, 
                                             category_url=category_url)
                new_category.put()                        
                cache.delete_cache()
                memcache.delete("sidebar")
                self.generate_js_action('Category created' , '' , 
                                        """new_cat('%s','%s');
                                        $('cat_form').reset();""" % (new_category.key(), category_name))


class DelCategory(BaseRequestHandler):
    """/admin/categories/del - Delete a category"""
    def post(self):
        category_key = self.request.get('id')
        if not category_key:
            self.generate_js_error('Category not selected')
        else:
            try:
                category = models.Category.get(db.Key(category_key))
            except db.Error:
                self.generate_js_error('Category not found')
                return
            if category is None:
                self.generate_js_error('Category not found')
                return
            #Remove Category from posts
            #cat_posts = models.Post.all().filter('categories',category)
            #for post in cat_posts:
            #    post.categories.remove(category.key())
            #    post.put()
            #Remove the category
            category.delete()
            cache.delete_cache()
            memcache.delete("sidebar")
            self.generate_js_action('Category deleted','', 
                             "$('%s').dispose();" % category_key)


class CommentsPage(BaseRequestHandler):
    """/admin/comments - List of comments"""
    def get(self):
           query = models.Comment.all().order('-date')
           #Pagination
           #total_count = query.count()
           total_count = functions.get_counter('global_comments_counter')
           offset = self.request.get('offset')
           navigation = functions.navigation(offset,total_count)
           of = (navigation['current']-1)*10
           comments = query.fetch(limit=10,offset=of)
           values = {
               'comments': comments,
               'navigation': navigation
           }
           self.generate('comments.html', values)


class DelComment(BaseRequestHandler):
    """/admin/comments/del - Delete a comment"""
    def post(self):
        comment_key = self.request.get('id')
        if not comment_key:
            self.generate_js_error('Comment not found')
        else:
            try:
                comment = models.Comment.get(db.Key(comment_key))
            except db.Error:
                self.generate_js_error('Comment not found')
            if not comment:
                self.generate_js_error('Comment not found')
            else:
                #delete the cached pages
                def del_comment():
                    parent = comment.parent()
                    parent.num_comments -= 1
                    parent.put()
                db.run_in_transaction(del_comment)
                cache.delete_cache()
                #delete the post
                comment.delete()
                self.generate_js_action('Comment deleted','',
                                 "$('%s').dispose();" % comment_key)


class PostsPage(BaseRequestHandler):
    """/admin/posts - List of posts"""
    def get(self, type='post'):
        query = models.Post.all().order('-created').filter('post_type', type)
        #Navigation
        #total_count = query.count()
        total_count = functions.get_counter('global_' + type + '_counter')
        offset = self.request.get('offset')
        navigation = functions.navigation(offset, total_count)
        of = (navigation['current']-1) * 10
        posts = query.fetch(limit=10, offset=of)
        values = {
            type + 's_list': 'yes',
            type: type,
            'type': type,
            'posts': posts,
            'navigation': navigation
        }
        self.generate('posts.html', values)


class DelPost(BaseRequestHandler):
    """/admin/posts/del - Delete a post"""
    def post(self, type='post'):
        post_key = self.request.get('id')
        if not post_key:
            self.generate_js_error(type + ' not found')
        else:
            try:
                post = models.Post.get(db.Key(post_key))
            except db.Error:
                self.generate_js_error(type + ' not found')
                return
            if not post:
                self.generate_js_error(type + ' not found')
            else:
                #delete the comments from post
                #for comment in models.Comment.gql('WHERE post=:1', post):
                #    comment.delete()
                #delete the cached pages
                cache.delete_cache(post)
                if not post.draft and type=='page':
                    memcache.delete("sidebar")
                #delete the post
                post.delete()
                self.generate_js_action(type + ' deleted', '' , "$('%s').dispose();""" %  post_key)


class CreatePostPage(BaseRequestHandler):
    """/admin/posts/new - Create a new post or page"""
    def get(self, type='post', err={}):
        values = { 
                  'new_' + type: 'yes',
                  'type': type,
                  'action':'/admin/' + type + 's/new',
                  'post_categories': models.Category.all(),
                  'title': 'Create New '+ type }
        values.update(err)
        self.generate('newpost.html', values)
    
    def post(self, type='post'):
        action, title, excerpt, text, post_categories = (self.request.get(item) for item in ('action', 
                                                                                             'title', 
                                                                                             'excerpt', 
                                                                                             'text', 
                                                                                             'categories'))
        if not action=='Publish' and not action=='Save':
            self.generate_js_error('Invalid data')
            return
        if not title:
            self.generate_js_error('Title required')
            return
        if not text:
            self.generate_js_error('Content required')
            return
        #Avoid invalid characters in permalink
        #TODO: Limit perpmalink to 40 characters && the permalink is unic
        pattern = re.compile('\W')
        permalink = re.sub(pattern,'-',title.lower())
        #check if exist a post whith same permalink
        post = models.Post.get_by_key_name( type + '_' + permalink)
        if post:
            permalink = permalink + '_'
        post=models.Post(key_name= type + '_' + permalink, content=unicode(text,'utf-8'), title=unicode(title,'utf-8') , creator=users.get_current_user())
        post.excerpt = unicode(excerpt,'utf-8')
        if post_categories:
            post.categories_keys=[db.Key(k) for k in post_categories.strip('+').split('+')]
        else:
            post.categories_keys=[]
        post.permalink = permalink
        post.post_type = type
        if not self.request.get('comments_enabled'):
            post.comments_enabled = False
        msg =''
        if action=="Publish":
            #publish info
            post.draft = False
            post.published = datetime.datetime.now()  
            if type=='page':  #remove sidebar from memcache 
                memcache.delete("sidebar")
                functions.increment_counter('global_page_counter')
            else: #delete cached pages and increment counters
                cache.delete_cache()
                functions.increment_counter('global_post_counter')
                for category in post.categories:
                    functions.increment_counter('cat_' + category.category_url + '_counter')
            msg = msg + "$('post_form_publish').setStyles({'display':'none'});"
        post.put()
        #build the response
        msg = msg + "$('post_form').setProperty('action', '/admin/"  + type + "s/edit');" #change the acion of the form
        msg += """$('post_key').value = '%s';""" %  post.key() #set the key of the post in the form for edit
        self.generate_js_action(type + ' saved', '', msg) #return 


class EditPostPage(CreatePostPage):
    """/admin/posts/edit - Edit a existing post"""
    def get(self, type='post'):
        post_key = self.request.get('id')
        if not post_key:
            CreatePostPage.get(self, type, {'errordesc': 'No post selected'})
        else:
            try:
                post = models.Post.get(db.Key(post_key))
            except db.Error:
                CreatePostPage.get(self, type, {'errordesc': 'Invalid data'})
                return
            if not post:
                CreatePostPage.get(self, type, {'errordesc': type + ' not found'})
            else:
                CreatePostPage.get(self, type, {
                                                'edit_' + type : 'yes',
                                                'post': post,
                                                'p_categories': '+'.join((str(cat.key()) for cat in post.categories)),
                                                'title': 'Edit Post',
                                                'action': '/admin/' + type + 's/edit',
                                                'key': post.key() })
    def post(self, type='post'):
        post_key = self.request.get('key')
        if not post_key:
             self.generate_js_error('No ' + type +' selected')
        else: #get the post
            try:
                post = models.Post.get(db.Key(post_key))
            except db.Error:
                self.generate_js_error('Invalid data')
                return
            if not post:
                self.generate_js_error('Invalid ' + type)
                return
            action, title, excerpt, text, post_categories = (self.request.get(item) for item in ('action', 'title', 'excerpt', 'text', 'categories'))
            if not action=='Publish' and not action=='Save':
                self.generate_js_error('Error processing data')
                return
            if not title:
                self.generate_js_error('Title is required')
                return
            if not text:
                self.generate_js_error('Content is required')
                return
            post.title = unicode(title, 'utf-8')
            post.excerpt = unicode(excerpt, 'utf-8')
            post.content = unicode(text, 'utf-8')
            old_post_categories = post.categories_keys #For change in counters
            if post_categories:
                post.categories_keys=[db.Key(k) for k in post_categories.strip('+').split('+')]
            else:
                post.categories_keys=[]
            msg = ''
            if action=='Publish':
                #publish info
                post.draft = False
                post.published = datetime.datetime.now()
                msg = msg + "$('post_form_publish').setStyles({'display':'none'});"
                if type=='page':  #remove sidebar from memcache 
                    memcache.delete("sidebar")
                    functions.increment_counter('global_page_counter')
                else: #delete cached pages and increment counters
                    cache.delete_cache()
                    functions.increment_counter('global_post_counter')
                    for category in post.categories:
                        functions.increment_counter('cat_' + category.category_url + '_counter')
                msg = msg + "$('post_form_publish').setStyles({'display':'none'});"
            elif not post.draft: # published post/page modified
                for cat in old_post_categories:
                    if cat not in post.categories_keys:
                        category = db.get(cat)
                        functions.decrement_counter('cat_' + category.category_url + '_counter')
                for cat in post.categories_keys:
                    if cat not in old_post_categories:
                        category = db.get(cat)
                        functions.increment_counter('cat_' + category.category_url + '_counter')                
            if not self.request.get('comments_enabled'):
                post.comments_enabled = False
            else:
                post.comments_enabled = True
            post.put()
            self.generate_js_action(type + ' saved', '', msg)


class Sidebar(BaseRequestHandler):
    """Pgae for set the components of the sidebar"""
    def get(self):
        options = functions.get_options()
        opt = options.sidebar_widgets
        widgets = models.SidebarWidget.all()
        active_widgets = []
        inactive_widgets = []
        def get_name(key):
            for widget in widgets:
                if str(widget.key()) == key:
                    return widget.name
        #active widgets
        for item in opt:
            if item in ("categories" , "pages", "links", "feed"):
                active_widgets.append({'id':item , 'name': item, 'class': 'default'})
            else:
                active_widgets.append({'id':item , 'name': get_name(item), 'class': 'widget'})
        #check default items
        for item in ("categories" , "pages", "links", "feed"):
            if item not in opt:
                inactive_widgets.append({'id':item , 'name': item, 'class': 'default'})
        for widget in widgets:
            widget_key = str(widget.key())
            if widget_key not in opt:
                inactive_widgets.append({'id':widget_key , 'name': widget.name, 'class': 'widget'})
        values = { 'inactive': inactive_widgets, 'active': active_widgets}
        self.generate('sidebar.html', values)
    
    def post(self):
        options = functions.get_options()
        errors = {}
        for name, value in self.request.params.items():
            errors.update(options.set_option(unicode(name,'utf-8'), unicode(value,'utf-8')))
        if errors:
            msg = ''
            for key in errors.keys():    
                msg = msg + ( "Growl.Error({title:'%s', text: '%s'});"  % (key, errors[key]) )
            self.generate_js(msg)  
        else:
            memcache.delete("sidebar")
            memcache.delete("options")
            self.generate_js_action('Sidebar updated')


class LinksPage(BaseRequestHandler):
    """/admin/sidebr/links - List of links"""
    def get(self):
        values = {
              'links': models.Link.all(),
              'form': models.LinkForm()
        }
        self.generate('links.html',values)


class NewLink(BaseRequestHandler):
    """/admin/sidebar/links/new - Create a new link"""
    def post(self):
        data = models.LinkForm(data=self.request.POST)
        if not data.is_valid():
            msg=''
            for key in data.errors.keys():
                msg=msg+ "Growl.Error({title:'%s', text: '%s'});" % (key, data.errors[key])
            self.generate_js(msg)
            return
        new_link = data.save()
        cache.delete_cache()
        memcache.delete("sidebar")
        
        self.generate_js_action('Link created', '',"""new_link('%s','%s','%s');$('link_form').reset();""" % ( new_link.key() , new_link.name, new_link.link ))


class DelLink(BaseRequestHandler):
    """/admin/sidebar/links/del - Delete a link"""
    def post(self):
        link_key = self.request.get('id')
        if not link_key:
            self.generate_js_error('Link not selected')
        else:
            try:
                link = models.Link.get(db.Key(link_key))
            except db.Error:
                self.generate_js_error('Link not found')
                return
            if link is None:
                self.generate_js_error('Link not found')
                return
            link.delete()
            cache.delete_cache()
            memcache.delete("sidebar")
            self.generate_js_action('Link deleted','',"$('%s').dispose();" % link_key)


class OptionsPage(BaseRequestHandler):
    """/admin/options - list of options"""
    def get(self):
        values = {'global_options': models.Option.all().filter('space', 'global') }
        self.generate('options.html', values )
    
    def post(self):
        options = functions.get_options()
        global_options = models.Option.all().filter('space', 'global')
        options_ok = []
        errors = {}
        for name, value in self.request.params.items():
            errors.update(options.set_option(unicode(name,'utf-8'), unicode(value,'utf-8')))
            options_ok.append(name)
        for opt in global_options:
            if opt.option_type == "boolean" and opt.name not in options_ok:
                errors.update(options.set_option(opt.name, "False"))
        if errors:
            msg = ''
            for key in errors.keys():    
                msg = msg + ( "Growl.Error({title:'%s', text: '%s'});"  % (key, errors[key]) )
            self.generate_js(msg)  
        else:
            self.generate_js_action('Options saved')  


class WidgetPage(BaseRequestHandler):
    """/admin/sidebar/widgets - list of wigetss"""
    def get(self):
        values = {
              'widgets': models.SidebarWidget.all(),
              'form': models.SidebarWidgetForm()
        }
        self.generate('widgets.html',values)        


class NewWidget(BaseRequestHandler):
    """/admin/sidebar/widgets/new - Create a new widget"""
    def post(self):
        data = models.SidebarWidgetForm(data=self.request.POST)
        if not data.is_valid():
            msg=''
            for key in data.errors.keys():
                msg=msg+ "Growl.Error({title:'%s', text: '%s'});" % (key, data.errors[key])
            self.generate_js(msg)
            return
        new_widget = data.save()
        title = new_widget.title
        if not title:
            title = ''
        new_widget.put()
        self.generate_js_action('Widget created','',"""
                            new_widget('%s','%s','%s', '%s');
                            """ % (new_widget.key() , new_widget.name, title, cgi.escape(new_widget.text).replace('\n','<br />')  ))


class DelWidget(BaseRequestHandler):
    """/admin/sidebar/widgets/del - Delete a widget"""
    def post(self):
        widget_key = self.request.get('id')
        if not widget_key:
            self.generate_js_error('Widget not selected')
        else:
            try:
                widget = models.SidebarWidget.get(db.Key(widget_key))
            except db.Error:
                self.generate_js_error('Widget not found')
                return
            if widget is None:
                self.generate_js_error('Widget not found')
                return
            widget.delete()
            cache.delete_cache()
            #memcache.delete("sidebar")
            self.generate_js_action('Widget deleted','' ,"$('%s').dispose();" % widget_key)


class EditWidget(BaseRequestHandler):
    """/admin/sidebar/widget/edit - edit a widget"""
    def post(self):
        widget_key = self.request.get('id')
        if not widget_key:
            self.generate_js_error('Widget not selected')
        else:
            try:
                widget = models.SidebarWidget.get(db.Key(widget_key))
            except db.Error:
                self.generate_js_error('Widget not found')
                return
            if widget is None:
                self.generate_js_error('Widget not found')
                return
            data = models.SidebarWidgetForm(data=self.request.POST, instance=widget)
            if not data.is_valid():
                msg=''
                for key in data.errors.keys():
                    msg=msg+ "Growl.Error({title:'%s', text: '%s'});" % (key, data.errors[key])
                self.generate_js(msg)
                return
            new_widget = data.save()
            title = new_widget.title
            if not title:
                title = ''
            self.generate_js("""Growl.Action({title:'%s'});
                                replace_widget('%s','%s','%s', '%s');
                                $('widget_form').reset();""" % ('Widget modified', new_widget.key() , new_widget.name, title, cgi.escape(new_widget.text).replace('\n','<br />')  ))


class ClearCache(BaseRequestHandler):
    """/admin/cache/del - Clear the cached pages"""
    def post(self):
        cache.delete_cache()
        self.generate_js_action('Cache Deleted')


class CheckOptions(BaseRequestHandler):
    """/admin/options/check - Checks if the defaults options are created"""
    def post(self):
        options = functions.get_options()
        options.set_defaults(False)
        self.generate_js_action('Options updated')


class CachePage(BaseRequestHandler):
    """/admin/options/cache - list of wigetss"""
    def get(self):
        values = {
              'cached_pages': models.CachedPage.all()
        }
        self.generate('cache.html',values)        


class CountersPage(BaseRequestHandler):
    """/admin/options/cache - list of wigetss"""
    def get(self):
        values = {
              'counters': models.Counter.all()
        }
        self.generate('counters.html',values)        


class MainAdminPage(BaseRequestHandler):
    """/admin/ - main admin page"""
    def get(self):
        self.generate('admin.html',{})


def main():
    application = webapp.WSGIApplication([(r'/admin/(post|page)s/',PostsPage),
                                        ('/admin/categories/new',NewCategory),
                                        ('/admin/categories/del',DelCategory),
                                        ('/admin/categories/',CategoriesPage),
                                        (r'/admin/(post|page)s/new',CreatePostPage),
                                        (r'/admin/(post|page)s/del',DelPost),
                                        (r'/admin/(post|page)s/edit',EditPostPage),
                                        ('/admin/comments/',CommentsPage),
                                        ('/admin/comments/del',DelComment),
                                        ('/admin/sidebar/',Sidebar),
                                        ('/admin/sidebar/links/',LinksPage),
                                        ('/admin/sidebar/links/new',NewLink),
                                        ('/admin/sidebar/links/del',DelLink),
                                        ('/admin/sidebar/widgets/',WidgetPage),
                                        ('/admin/sidebar/widgets/new',NewWidget),
                                        ('/admin/sidebar/widgets/del',DelWidget),
                                        ('/admin/sidebar/widgets/edit',EditWidget),
                                        ('/admin/options/', OptionsPage),
                                        ('/admin/options/check', CheckOptions),
                                        ('/admin/options/cache/', CachePage),
                                        ('/admin/options/cache/del', ClearCache),
                                        ('/admin/options/counters/', CountersPage),
                                        ('/admin/', MainAdminPage),
                                        ('/admin/(.*)',NotFound)],
                                        debug=True)
    wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
  main()